package org.tulkas.http;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

import org.apache.http.client.cache.HttpCacheEntry;
import org.apache.http.client.cache.HttpCacheStorage;
import org.apache.http.client.cache.HttpCacheUpdateCallback;
import org.apache.http.client.cache.ResourceFactory;
import org.apache.http.impl.client.cache.CacheConfig;
import org.apache.http.impl.client.cache.FileResourceFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tulkas.flusher.IFlusher;
import org.tulkas.util.FileUtil;

/**
 * @author <a href="mailto:tulkas.web@gmail.com">Tulkas</a>
 *
 */
public class CacheStorage implements HttpCacheStorage, IFlusher {
	public static final String CACHE_DIR_NAME = "cache";
	public static final String CACHE_INDEX_FILE = "httpCache.index";

	private static Logger logger = LoggerFactory.getLogger(CacheStorage.class);

	private final String clientName;
	private final ResourceFactory resourceFactory;
	private final CacheMap<String, HttpCacheEntry> entries;
	private boolean modified = false;

	public CacheStorage(String clientName, CacheConfig config) {
		super();
		this.entries = new CacheMap<String, HttpCacheEntry>(config
				.getMaxCacheEntries());
		this.clientName = clientName;
		File cacheDir = new File(this.getCacheDir());
		cacheDir.mkdirs();
		resourceFactory = new FileResourceFactory(cacheDir);
		loadIndex();
	}

	public synchronized ResourceFactory getResourceFactory() {
		return resourceFactory;
	}

	public void flush() {
		saveIndex();
	}

	public boolean isAvaliable() {
		return true;
	}

	public synchronized boolean isModified() {
		return modified;
	}

	private String getCacheDir() {
		return ClientManager.getInstance().getClientPath(clientName) + "/"
				+ CACHE_DIR_NAME;
	}

	public synchronized void saveIndex() {
		String persistFile = getCacheDir() + "/" + CACHE_INDEX_FILE;
		try {
			new File(FileUtil.getFileDir(persistFile)).mkdirs();
			ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(
					persistFile));
			ZipEntry entry = new ZipEntry(this.getClass().getName());
			zos.putNextEntry(entry);
			ObjectOutputStream oos = new ObjectOutputStream(zos);
			oos.writeInt(entries.size());
			Iterator<Entry<String, HttpCacheEntry>> it = entries.entrySet()
					.iterator();
			while (it.hasNext()) {
				Entry<String, HttpCacheEntry> ent = it.next();
				oos.writeObject(ent.getKey());
				oos.writeObject(ent.getValue());
			}
			zos.closeEntry();
			oos.close();

		} catch (IOException e) {
			logger.error("Error saving cash index file [" + persistFile + "]!",
					e);
		}
	}

	public synchronized void loadIndex() {
		String persistFile = getCacheDir() + "/" + CACHE_INDEX_FILE;
		File file = new File(persistFile);
		try {
			if (file.exists()) {
				ZipInputStream zis = new ZipInputStream(new FileInputStream(
						file));
				if (zis.getNextEntry() != null) {
					ObjectInputStream ois = new ObjectInputStream(zis);
					int count = ois.readInt();
					for (int i = 0; i < count; i++) {
						String key = (String) ois.readObject();
						HttpCacheEntry value = (HttpCacheEntry) ois
								.readObject();
						entries.put(key, value);
					}
				}
				zis.closeEntry();
				zis.close();
			}
		} catch (IOException e) {
			logger.error("Error loading cash index file [" + file.toString()
					+ "]!", e);
		} catch (ClassNotFoundException e) {
			logger.error("Error loading cash index file [" + file.toString()
					+ "]!", e);
		}
	}

	/**
	 * Places a HttpCacheEntry in the cache
	 *
	 * @param url
	 *            Url to use as the cache key
	 * @param entry
	 *            HttpCacheEntry to place in the cache
	 */
	public synchronized void putEntry(String url, HttpCacheEntry entry)
			throws IOException {
		entries.put(url, entry);
		modified = true;
	}

	/**
	 * Gets an entry from the cache, if it exists
	 *
	 * @param url
	 *            Url that is the cache key
	 * @return HttpCacheEntry if one exists, or null for cache miss
	 */
	public synchronized HttpCacheEntry getEntry(String url) throws IOException {
		return entries.get(url);
	}

	/**
	 * Removes a HttpCacheEntry from the cache
	 *
	 * @param url
	 *            Url that is the cache key
	 */
	public synchronized void removeEntry(String url) throws IOException {
		entries.remove(url);
		modified = true;
	}

	public synchronized void clear() {
		entries.clear();
		FileUtil.delTree(getCacheDir());
		new File(getCacheDir()).mkdirs();
		modified = true;
	}

	public synchronized void updateEntry(String url,
			HttpCacheUpdateCallback callback) throws IOException {
		HttpCacheEntry existingEntry = entries.get(url);
		entries.put(url, callback.update(existingEntry));
		modified = true;
	}
}
